/*
    多线程统计 Linux 内核中 .c、.h 文件的文件数及文件的总行和
    基于线程池+生产者消费者模型，主线程递归遍历目录，将.c/.h文件路径放入队列，线程池线程统计行数并累加（每线程私有统计，主线程合并）
*/

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <ctype.h>

#define THREAD_NUM 4

// 任务节点
typedef struct tasknode
{
    char* filename;
    struct tasknode* next;
}task_node;

//任务队列
typedef struct 
{
    task_node* head, *tail;
    pthread_mutex_t mut;
    pthread_cond_t cond;
    int stop;   //停止标志位
}task_queue;

// 统计结果
typedef struct 
{
    int c_lines, h_lines;
    int c_count, h_count;
    int code_lines;      // 代码行
    int single_comment;  // 单行注释
    int multi_comment;   // 多行注释
    int empty_lines;     // 空行
}result_t;

typedef struct 
{
    int file;
    int folder;
    int link;
}file_folder;


task_queue queue;
pthread_t threads[THREAD_NUM];
result_t result[THREAD_NUM];
file_folder sum = {0};  // 初始化为0

void task_queue_init(task_queue* queue);
void task_queue_destroy(task_queue *q);
int is_source_file(const char *filename);
char* task_queue_pop(task_queue *q);
void task_queue_push(task_queue *q, const char *filename);
void* worker(void* avg);
// 递归遍历目录，将.c/.h文件路径放入队列
void traverse(const char *dir);
void count_line_types(char* buffer, int* code, int* empty_lines, int* signal_lines);

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        fprintf(stderr ,"参数错误 eg : %s <文件路径>\n", argv[0]);
        exit(1);
    }
    task_queue_init(&queue);
    // 创建线程池
    int tid[THREAD_NUM];
    for(int i = 0; i < THREAD_NUM; i++)
    {
        tid[i] = i;
        // 用来知道是几号线程
        pthread_create(&threads[i], NULL, worker, &tid[i]);
    }
    // 主线程 发送任务到队列
    traverse(argv[1]);

    // 通知线程池退出
    pthread_mutex_lock(&queue.mut);
    queue.stop = 1;
    pthread_cond_broadcast(&queue.cond);
    pthread_mutex_unlock(&queue.mut);
    // 等待线程池结束
    for (int i = 0; i < THREAD_NUM; ++i) {
        pthread_join(threads[i], NULL);
    }
    //主线程进行统计
    int total_c_count = 0, total_h_count = 0, total_c_lines = 0, total_h_lines = 0;
    int total_code_lines = 0, total_single_comment = 0, total_multi_comment = 0, total_empty_lines = 0;
    for (int i = 0; i < THREAD_NUM; ++i) {
        total_c_count += result[i].c_count;
        total_h_count += result[i].h_count;
        total_c_lines += result[i].c_lines;
        total_h_lines += result[i].h_lines;
        total_code_lines += result[i].code_lines;
        total_single_comment += result[i].single_comment;
        total_multi_comment += result[i].multi_comment;
        total_empty_lines += result[i].empty_lines;

    }
    printf("%s中%d个文件夹，%d个文件, %d个链接\n",argv[1], sum.folder, sum.file, sum.link);
    printf("%s中包含%d个.c文件，共%d行，%d个.h文件，共%d行\n", argv[1], total_c_count, total_c_lines, total_h_count, total_h_lines);
    printf("代码行：%d，单行注释：%d，多行注释：%d，空行：%d\n", total_code_lines, total_single_comment, total_multi_comment, total_empty_lines);
    task_queue_destroy(&queue);
    return 0;
}

// 判断是否是 .c 或 .h 文件，返回1为.c，2为.h，否则0
int is_source_file(const char *filename) {
    int len = strlen(filename);
    if (len >= 2 && filename[len - 2] == '.' && filename[len - 1] == 'c') return 1;
    if (len >= 2 && filename[len - 2] == '.' && filename[len - 1] == 'h') return 2;
    return 0;
}

void task_queue_init(task_queue* queue)
{
    queue->head = queue->tail = NULL;
    pthread_mutex_init(&queue->mut, NULL);
    pthread_cond_init(&queue->cond, NULL);
    queue->stop = 0;
}

void task_queue_destroy(task_queue *q)
{
    pthread_mutex_destroy(&q->mut);
    pthread_cond_destroy(&q->cond);
}

void task_queue_push(task_queue *q, const char *filename)
{
    task_node* node = (task_node*)malloc(sizeof(task_node));
    node->filename = strdup(filename);
    node->next = NULL;
    pthread_mutex_lock(&q->mut);
    if(q->tail)
        q->tail->next = node;
    else
        q->head = node;
    q->tail = node;
    //唤醒 非空
    pthread_cond_signal(&q->cond);
    pthread_mutex_unlock(&q->mut);
}

char* task_queue_pop(task_queue *q)
{
    pthread_mutex_lock(&q->mut);
    //头节点为空，并且没有停止
    while(!q->head && !q->stop)
    {
        //说明没有节点，让出锁，进入阻塞
        pthread_cond_wait(&q->cond, &q->mut);
    }
    //如果 停止并且队列中没有数据，结束
    if(q->stop && !q->head)
    {
        pthread_mutex_unlock(&q->mut);
        return NULL;
    }
    task_node* node = q->head;
    q->head = q->head->next;
    //如果头结点为空
    if(!q->head)
        q->tail = NULL;
    pthread_mutex_unlock(&q->mut);
    char* filename = node->filename;
    free(node);
    return filename;
}

void traverse(const char *dir)
{
    //打开目录流
    DIR* d = opendir(dir);
    if(!d)
    {
        perror("打开目录失败！\n");
        exit(1);
    }
    struct dirent* read_dir;
    char name[1024];
    while(read_dir = readdir(d))
    {
        // 文件名为 . 或者 .. 的跳过
        if(!strcmp(read_dir->d_name, ".") || !strcmp(read_dir->d_name, ".."))
            continue;
        snprintf(name, sizeof(name), "%s/%s", dir, read_dir->d_name);
        if(read_dir->d_type == 4)
        {
            sum.folder++;
            traverse(name);
        }
        if(read_dir->d_type == 8)
        {
            sum.file++;
            int len = strlen(read_dir->d_name);
            if(len >= 2) {
                const char* suffix = read_dir->d_name + len - 2;
                int is_c = !strcmp(suffix, ".c");
                int is_h = !strcmp(suffix, ".h");
                if(is_c || is_h)
                    task_queue_push(&queue, name);
            }
        }
        if(read_dir->d_type == 10)
            sum.link++;
    }
    closedir(d);
}

void* worker(void* avg)
{
    int tid = *(int*)avg;
    //每个线程都有一个结果集，计算不会导致多线程，最后子线程进行累加
    result_t* res = &result[tid];
    res->c_count = res->c_lines = 0;
    res->h_count = res->h_lines = 0;
    res->code_lines = res->empty_lines = res->multi_comment = res->single_comment = 0;
    while(1)
    {
        char* filename = task_queue_pop(&queue);
        //表示结束了，并且链表中没有数据
        if(!filename)   break;
        int line = 0;
        int type = 0;
        int code = 0,single_comment = 0, multi_comment = 0, empty_lines = 0;
        int in_multi_comment = 0;   //标记是否在多行注释中
        FILE* fp = fopen(filename, "r");
        if(!fp)
        {
            fprintf(stderr, "文件%s打开失败！\n", filename);
            continue;
        }
        //统计行数，行为单位读取
        char buffer[1024];
        while(fgets(buffer, sizeof(buffer), fp) != NULL)
        {
            line++;
            //是否在多行注释中
            if(in_multi_comment)
            {
                //看是否是结束行
                if(strstr(buffer, "*/"))
                {
                    in_multi_comment = 0;
                    multi_comment++;
                }
                else
                {
                    multi_comment++;
                }
            }
            else    //没有在多行注释中
            {
                if(strstr(buffer, "/*"))
                {
                    in_multi_comment = 1;
                    multi_comment++;
                }
                else
                {
                    count_line_types(buffer, &code, &empty_lines, &single_comment);
                }
            }
        }
        fclose(fp);
        if(is_source_file(filename) == 1)
        {
            res->c_count++;
            res->c_lines += line;
        }
        if(is_source_file(filename) == 2)
        {
            res->h_count++;
            res->h_lines += line;
        }
        res->empty_lines += empty_lines;
        res->code_lines += code;
        res->single_comment += single_comment;
        res->multi_comment += multi_comment;
        printf("%s 文件中有%d行数据 其中代码行 %d, 单行注释 %d, 多行注释 %d, 空行 %d\n", filename, line, code, single_comment, multi_comment, empty_lines);
        free(filename);
    }
    return NULL;
}

//检查除多行注释外的其他行
void count_line_types(char* buffer, int* code, int* empty_lines, int* signal_lines)
{
    int len = strlen(buffer);
    //将前导的空白行去除
    int i = 0;
    while(i < len && isspace(buffer[i]))
        i++;
    //空行
    if(i >= len)
    {
        (*empty_lines)++;
        return;
    }
    if(i < len-1 && buffer[i] == '/' && buffer[i+1] == '/')
    {
        (*signal_lines)++;
        return;
    }
    //其他都是代码行
    (*code)++;
}
